<?php
/***
 * Candy框架 数据库PDO驱动类	
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2019-08-05 10:48:32 $   
 */

declare(strict_types=1);
namespace Candy\Extend\DB\Driver;
use Candy\Extend\DB\DBServer;
use Candy\Extend\Cache\CacheServer;
use Candy\Core\Debug;

defined('CANDY') OR die('You Are A Bad Guy. o_O???');

Class Sqlite extends DBServer {
	static $count = 0;
	static $execute = 0;
	static $totalcate = 0;
	static $links = [];
	static $results = null;
	
	/**
	 * 用于获取数据库连接PDO对象,如果已经存在PDO对象就不在调用connect()去连接
	 */
	protected function connect(): object
	{
		//检查驱动
		if(!extension_loaded('pdo')){
			Debug::showMessage('请开启PDO扩展。', 'SqlLite');
		}
		
		$this->setLink();
		if(empty(self::$links['default'])){
			$pdo = $this->getDB();
			self::$links['default'] = $pdo;
			return $pdo;
		} else {
			return self::$links['default'];
		}	
	}
	
	/**
	 * 获取连接对象
	 */
	protected function getDB()
	{
		self::$count++;
		Debug::addMsg('<b>第 '.self::$count.' 次连接数据库</b>', 2); 
		
		//数据库位置
		$dsn= $this->dbname ?: CANDYROOT.'Date/queue.db';
		if (!file_exists($this->$dsn)) {
			//创建目录
			\Candy\Extend\Dir::create(dirname($dsn) . '/');
			if (!($fp = fopen($dsn, "w+"))){
				Debug::showMessage($e->getMessage(), 'SqlLite');
				return ;
			}
			fclose($fp);
		}
		
		$pdo = new \PDO('sqlite:'.$dsn);
		return $pdo;
	}
	
	/**
	 * 执行SQL语句的方法
	 *
	 * @param	string	$sql		用户查询的SQL语句
	 * @param	string	$method		SQL语句的类型（select,find,total,insert,update,other）
	 * @param	array	$data		为prepare方法中的?参数绑定值
	 * @return	mixed			根据不同的SQL语句返回值
	 */
	public function query(string $sql,string $method = 'other',array $data = []): array|int|string|bool
	{
		$startTime = microtime(true); 
		$this->setNull(); //初使化sql
		$value = $this->escapeStringArray($data);
		$marr = explode('::', $method);
		$method = strtolower(array_pop($marr));
		
		if(strtolower($method) == trim('total')){
			$sql = preg_replace('/select.*?from/i','SELECT count(*) as count FROM',$sql);
		}
		$addcache = false;
		$memkey = $this->sql($sql, $value);
		
		//随机语句不缓存
		$rand = stristr($memkey,'rand()');
		
		//转化key
		$thekey = md5($memkey);
		
		//如果之前已查询被缓存则直接返回
		if($rand === false){
			if(isset(self::$results[$this->tabName][$thekey])){
				return self::$results[$this->tabName][$thekey];
			}elseif($method == 'select' || $method == 'find' || $method=='total'){
				$addcache = true;
			}
		}
		
		//mem取缓存
		if(defined('CLIWORKING') === false){
			$memCache = '';
			$memName = 'File';
			if($rand === false){
				$memCache = CacheServer::instance();
				$memName = explode('\\', $memCache::class)[4];
				if($memName != 'File'){
					if($method == 'select' || $method == 'find' || $method=='total'){
						$data = $memCache->getCache($thekey);
						if($data){
							return $data;  //直接从memserver中取，不再向下执行
						}else{
							$addcache = true;	
						}
					}
				}
			}
		}
		
		try{
			$return = null;
 			$pdo = $this->connect();
	 		$stmt = $pdo->prepare($sql);  //准备好一个语句
	        $result = $stmt->execute($value);   //执行一个准备好的语句
		
			//不是查找语句
			if($addcache === false){
				self::$results[$this->tabName] = '';
			}
			
			//如果使用mem，并且不是查找语句
			if(!empty($memCache) && $addcache === false && $memName != 'File' && defined('CLIWORKING') === false){
				if($stmt->rowCount()>0){
					$memCache->delCache($this->tabName);	 //清除缓存
					Debug::addMsg("清除表<b>{$this->tabName}</b>在Memcache中所有缓存!"); //debug
				}
			}
			
			switch($method){
				case 'select':  //查所有满足条件的
					$data = $stmt->fetchAll(\PDO::FETCH_ASSOC);
					$return = $data;
					break;
				case 'find':    //只要一条记录的
					$data = $stmt->fetch(\PDO::FETCH_ASSOC);
					$return = $data;
					break;
				case 'total':  //返回总记录数
					$row = $stmt->fetch(\PDO::FETCH_NUM);
					$data = (int)$row[0];
					$return = $data;
					break;
				case 'insert':  //插入数据 返回最后插入的ID
					if($this->auto == 'yes')
						$return = $pdo->lastInsertId();
					else
						$return = $result;
					break;
				case 'delete':
				case 'update':        //update 
					$return = $stmt->rowCount();
					break;
				default:
					$return = $result;
			}
			
			self::$execute++;
			$stopTime = microtime(true);
			$ys = round(($stopTime - $startTime)*1000, 4);
			
			//添加到缓存
			if($method == 'select' || $method == 'find' || $method=='total'){
				//自己缓存
				self::$results[$this->tabName][$thekey] = $data;
				
				//不是workerman模式
				if(defined('CLIWORKING') === false){
					//mem缓存
					if($addcache && $rand === false && $memName != 'File'){
						$memCache->addCache($this->tabName, $thekey, $data);
					}
				}
			}
			
			Debug::addMsg('[用时<font color=\'red\'>'.$ys.'</font>ms] ('. self::$execute .'次) - '.$memkey,2); //debug
			self::$totalcate += $ys;
			self::$echosql = $memkey;
			return $return;
		}catch(\PDOException $e){
			Debug::showMessage($e->getMessage(), 'Mysql');
		}	
	}
	
	/**
	 * 自动获取表结构
	 */ 
	public function setTable(string $tabName): void
	{
		$cachefile = RUNTIME . 'Data/'.$tabName.'.php';
		if(file_exists($cachefile)){
			$this->setLink();
			$json = ltrim(file_get_contents($cachefile),'<?php ');
			$this->auto = substr($json,-3);
			$json = substr($json, 0, -3);
			$this->fieldList = (array)json_decode($json, true);	
			$this->tabName = $this->tabprefix.$tabName; //加前缀的表名
		}else{
			try{
				$pdo = $this->connect();
				if(is_null($pdo)){
					//数据库连接失败
					stop();
				}
				$this->tabName = $this->tabprefix.$tabName; //加前缀的表名
				$stmt = $pdo->prepare("desc {$this->tabName}");
				
				if(is_bool($stmt)){
					Debug::showMessage('数据库有问题请检查', 'SqlLite');
					return ;
				}
				
				$stmt->execute();
				$auto = 'yno';
				$fields = [];
				while($row = $stmt->fetch(\PDO::FETCH_ASSOC)){
					if($row['Key'] == 'PRI'){
						$fields['pri'] = strtolower($row['Field']);
					}else{
						$fields[] = strtolower($row['Field']);
					}
					if($row['Extra'] == 'auto_increment')
						$auto = 'yes';
				}
				//如果表中没有主键，则将第一列当作主键
				if(!array_key_exists('pri', $fields)){
					$fields['pri'] = array_shift($fields);		
				}
				if(isDebug() === false){
					file_put_contents($cachefile, '<?php '.json_encode($fields).$auto);
				}
				$this->fieldList = $fields;
				$this->auto = $auto;
			}catch(\PDOException $e){
				Debug::showMessage($e->getMessage(), 'Mysql');
			}
		}
	}
	
	/**
	 * 事务开始
	 */
	public function beginTransaction(): void
	{
		$pdo = $this->connect();
		$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, 0); 
		$pdo->beginTransaction();
	}
	
	/**
 	 * 事务提交
 	 */
	public function commit(): void
	{
		$pdo = $this->connect();
		$pdo->commit();
		$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, 1); 
	}
	
	/**
	 * 事务回滚
	 */
	public function rollBack(): void
	{
		$pdo = $this->connect();
		$pdo->rollBack();
		$pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, 1); 
	}
	
	/*
	 * 获取数据库使用大小
	 *
	 * @return	string		返回转换后单位的尺寸
	 */
	public function dbSize(): string
	{
		$sql = 'SHOW TABLE STATUS FROM ' . G('DBNAME');
		if(!empty($this->tabprefix)){
			$sql .= ' LIKE \''.$this->tabprefix.'%\'';
		}
		$pdo = $this->connect();
		$stmt = $pdo->prepare($sql);  //准备好一个语句
	    $stmt->execute();   //执行一个准备好的语句
		$size = 0;
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC))
			$size += $row['Data_length'] + $row['Index_length'];
		return tosize($size);
	}

	/*
	 * 数据库的版本 
	 *
	 * @return	string		返回数据库系统的版本
	 */
	public function dbVersion(): string
	{
		$pdo = $this->connect();
		return $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION);
	}
}
